/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package org.apache.ranger.biz;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.hadoop.security.SecureClientLogin;
import org.apache.ranger.common.PropertiesUtil;
import org.apache.ranger.common.TimedExecutor;
import org.apache.ranger.db.XXGroupUserDao;
import org.apache.ranger.entity.XXGroupUser;
import org.apache.ranger.plugin.client.HadoopConfigHolder;
import org.apache.ranger.plugin.client.HadoopException;
import org.apache.ranger.plugin.model.RangerSecurityZone;
import org.apache.ranger.plugin.model.RangerService;
import org.apache.ranger.plugin.model.RangerServiceDef;
import org.apache.ranger.plugin.service.RangerBaseService;
import org.apache.ranger.plugin.service.RangerDefaultService;
import org.apache.ranger.plugin.service.ResourceLookupContext;
import org.apache.ranger.plugin.store.EmbeddedServiceDefsUtil;
import org.apache.ranger.plugin.store.ServiceStore;
import org.apache.ranger.plugin.util.RangerRoles;
import org.apache.ranger.plugin.util.RangerRolesUtil;
import org.apache.ranger.service.RangerServiceService;
import org.apache.ranger.services.tag.RangerServiceTag;
import org.apache.ranger.view.VXMessage;
import org.apache.ranger.view.VXResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;

import static org.apache.ranger.plugin.policyengine.RangerPolicyEngine.GROUP_PUBLIC;

@Component
public class ServiceMgr {
    private static final Logger LOG = LoggerFactory.getLogger(ServiceMgr.class);

    private static final String LOOKUP_PRINCIPAL     = "ranger.lookup.kerberos.principal";
    private static final String LOOKUP_KEYTAB        = "ranger.lookup.kerberos.keytab";
    private static final String ADMIN_USER_PRINCIPAL = "ranger.admin.kerberos.principal";
    private static final String ADMIN_USER_KEYTAB    = "ranger.admin.kerberos.keytab";
    private static final String AUTHENTICATION_TYPE  = "hadoop.security.authentication";
    private static final String KERBEROS_TYPE        = "kerberos";
    private static final String NAME_RULES           = "hadoop.security.auth_to_local";
    private static final String HOST_NAME            = "ranger.service.host";

    private static final long _DefaultTimeoutValue_Lookp          = 1000; // 1 s
    private static final long _DefaultTimeoutValue_ValidateConfig = 10000; // 10 s

    private static final Map<String, Class<? extends RangerBaseService>> serviceTypeClassMap = new HashMap<>();

    @Autowired
    RangerServiceService rangerSvcService;

    @Autowired
    ServiceDBStore svcDBStore;

    @Autowired
    TagDBStore tagStore;

    @Autowired
    RoleDBStore rolesStore;

    @Autowired
    TimedExecutor timedExecutor;

    @Autowired
    RangerBizUtil rangerBizUtil;

    @Autowired
    SecurityZoneDBStore zoneStore;

    @Autowired
    XXGroupUserDao groupUserDao;

    public List<String> lookupResource(String serviceName, ResourceLookupContext context, ServiceStore svcStore) throws Exception {
        List<String>  ret     = null;
        RangerService service = svcDBStore.getServiceByName(serviceName);

        String authType        = PropertiesUtil.getProperty(AUTHENTICATION_TYPE);
        String lookupPrincipal = SecureClientLogin.getPrincipal(PropertiesUtil.getProperty(LOOKUP_PRINCIPAL), PropertiesUtil.getProperty(HOST_NAME));
        String lookupKeytab    = PropertiesUtil.getProperty(LOOKUP_KEYTAB);
        String nameRules       = PropertiesUtil.getProperty(NAME_RULES);
        String rangerPrincipal = SecureClientLogin.getPrincipal(PropertiesUtil.getProperty(ADMIN_USER_PRINCIPAL), PropertiesUtil.getProperty(HOST_NAME));
        String rangerkeytab    = PropertiesUtil.getProperty(ADMIN_USER_KEYTAB);

        if (!StringUtils.isEmpty(authType) && KERBEROS_TYPE.equalsIgnoreCase(authType.trim()) && SecureClientLogin.isKerberosCredentialExists(lookupPrincipal, lookupKeytab)) {
            if (service != null && service.getConfigs() != null) {
                service.getConfigs().put(HadoopConfigHolder.RANGER_LOOKUP_PRINCIPAL, lookupPrincipal);
                service.getConfigs().put(HadoopConfigHolder.RANGER_LOOKUP_KEYTAB, lookupKeytab);
                service.getConfigs().put(HadoopConfigHolder.RANGER_NAME_RULES, nameRules);
                service.getConfigs().put(HadoopConfigHolder.RANGER_AUTH_TYPE, authType);
            }
        }

        if (!StringUtils.isEmpty(authType) && KERBEROS_TYPE.equalsIgnoreCase(authType.trim()) && SecureClientLogin.isKerberosCredentialExists(rangerPrincipal, rangerkeytab)) {
            if (service != null && service.getConfigs() != null) {
                service.getConfigs().put(HadoopConfigHolder.RANGER_PRINCIPAL, rangerPrincipal);
                service.getConfigs().put(HadoopConfigHolder.RANGER_KEYTAB, rangerkeytab);
                service.getConfigs().put(HadoopConfigHolder.RANGER_NAME_RULES, nameRules);
                service.getConfigs().put(HadoopConfigHolder.RANGER_AUTH_TYPE, authType);
            }
        }

        RangerBaseService svc = null;

        if (service != null) {
            Map<String, String> newConfigs = rangerSvcService.getConfigsWithDecryptedPassword(service);

            service.setConfigs(newConfigs);

            svc = getRangerServiceByService(service, svcStore);
        }

        LOG.debug("==> ServiceMgr.lookupResource for Service: ({}Context: {})", svc, context);

        if (svc != null) {
            if (StringUtils.equals(svc.getServiceDef().getName(), EmbeddedServiceDefsUtil.EMBEDDED_SERVICEDEF_TAG_NAME)) {
                ret = svc.lookupResource(context);
            } else {
                LookupCallable callable = new LookupCallable(svc, context);
                long           time     = getTimeoutValueForLookupInMilliSeconds(svc);

                ret = timedExecutor.timedTask(callable, time, TimeUnit.MILLISECONDS);
            }
        }

        LOG.debug("==> ServiceMgr.lookupResource for Response: ({})", ret);

        return ret;
    }

    public VXResponse validateConfig(RangerService service, ServiceStore svcStore) throws Exception {
        VXResponse ret             = new VXResponse();
        String     authType        = PropertiesUtil.getProperty(AUTHENTICATION_TYPE);
        String     lookupPrincipal = SecureClientLogin.getPrincipal(PropertiesUtil.getProperty(LOOKUP_PRINCIPAL), PropertiesUtil.getProperty(HOST_NAME));
        String     lookupKeytab    = PropertiesUtil.getProperty(LOOKUP_KEYTAB);
        String     nameRules       = PropertiesUtil.getProperty(NAME_RULES);
        String     rangerPrincipal = SecureClientLogin.getPrincipal(PropertiesUtil.getProperty(ADMIN_USER_PRINCIPAL), PropertiesUtil.getProperty(HOST_NAME));
        String     rangerkeytab    = PropertiesUtil.getProperty(ADMIN_USER_KEYTAB);

        if (!StringUtils.isEmpty(authType) && KERBEROS_TYPE.equalsIgnoreCase(authType.trim()) && SecureClientLogin.isKerberosCredentialExists(lookupPrincipal, lookupKeytab)) {
            if (service != null && service.getConfigs() != null) {
                service.getConfigs().put(HadoopConfigHolder.RANGER_LOOKUP_PRINCIPAL, lookupPrincipal);
                service.getConfigs().put(HadoopConfigHolder.RANGER_LOOKUP_KEYTAB, lookupKeytab);
                service.getConfigs().put(HadoopConfigHolder.RANGER_NAME_RULES, nameRules);
                service.getConfigs().put(HadoopConfigHolder.RANGER_AUTH_TYPE, authType);
            }
        }

        if (!StringUtils.isEmpty(authType) && KERBEROS_TYPE.equalsIgnoreCase(authType.trim()) && SecureClientLogin.isKerberosCredentialExists(rangerPrincipal, rangerkeytab)) {
            if (service != null && service.getConfigs() != null) {
                service.getConfigs().put(HadoopConfigHolder.RANGER_PRINCIPAL, rangerPrincipal);
                service.getConfigs().put(HadoopConfigHolder.RANGER_KEYTAB, rangerkeytab);
                service.getConfigs().put(HadoopConfigHolder.RANGER_NAME_RULES, nameRules);
                service.getConfigs().put(HadoopConfigHolder.RANGER_AUTH_TYPE, authType);
            }
        }

        RangerBaseService svc = null;

        if (service != null) {
            Map<String, String> newConfigs = rangerSvcService.getConfigsWithDecryptedPassword(service);

            service.setConfigs(newConfigs);

            svc = getRangerServiceByService(service, svcStore);
        }

        LOG.debug("==> ServiceMgr.validateConfig for Service: ({})", svc);

        // check if service configs contains localhost/127.0.0.1
        if (service != null && service.getConfigs() != null) {
            for (Map.Entry<String, String> entry : service.getConfigs().entrySet()) {
                if (entry.getValue() != null && StringUtils.containsIgnoreCase(entry.getValue(), "localhost") || StringUtils.containsIgnoreCase(entry.getValue(), "127.0.0.1")) {
                    URL url = getValidURL(entry.getValue());

                    if ((url != null) && (url.getHost().equalsIgnoreCase("localhost") || url.getHost().equals("127.0.0.1"))) {
                        throw new Exception("Invalid value for configuration " + entry.getKey() + ": host " + url.getHost() + " is not allowed");
                    }
                }
            }
        }

        if (svc != null) {
            try {
                // Timeout value use during validate config is 10 times that used during lookup
                long                time         = getTimeoutValueForValidateConfigInMilliSeconds(svc);
                ValidateCallable    callable     = new ValidateCallable(svc);
                Map<String, Object> responseData = timedExecutor.timedTask(callable, time, TimeUnit.MILLISECONDS);

                ret = generateResponseForTestConn(responseData, "");
            } catch (Exception e) {
                Map<String, Object> respData = (e instanceof HadoopException) ? ((HadoopException) e).getResponseData() : new HashMap<>();
                String              msg;

                if (StringUtils.contains(e.getMessage(), RangerDefaultService.ERROR_MSG_VALIDATE_CONFIG_NOT_IMPLEMENTED)) {
                    msg = RangerDefaultService.ERROR_MSG_VALIDATE_CONFIG_NOT_IMPLEMENTED + " for " + svc.getServiceType();
                } else {
                    msg = "Unable to connect repository with given config for " + svc.getServiceName();
                }

                ret = generateResponseForTestConn(respData, msg);

                LOG.error("==> ServiceMgr.validateConfig Error:{}", String.valueOf(e));
            }
        }

        LOG.debug("==> ServiceMgr.validateConfig for Response: ({})", ret);

        return ret;
    }

    public boolean isZoneAdmin(String zoneName) {
        boolean            isZoneAdmin  = false;
        RangerSecurityZone securityZone = null;

        try {
            securityZone = zoneStore.getSecurityZoneByName(zoneName);
        } catch (Exception e) {
            LOG.error("Unexpected error when fetching security zone with name:[{}] from database", zoneName, e);
        }

        if (securityZone != null) {
            String userId = rangerBizUtil.getCurrentUserLoginId();

            if (securityZone.getAdminUsers() != null && securityZone.getAdminUsers().contains(userId)) {
                isZoneAdmin = true;
            }

            Set<String> loggedInUsersGroups = Collections.emptySet();

            if (!isZoneAdmin && securityZone.getAdminUserGroups() != null) {
                List<XXGroupUser> groupUsers = groupUserDao.findByUserId(rangerBizUtil.getXUserId());

                loggedInUsersGroups = new HashSet<>();

                loggedInUsersGroups.add(GROUP_PUBLIC);

                if (groupUsers != null) {
                    for (XXGroupUser groupUser : groupUsers) {
                        loggedInUsersGroups.add(groupUser.getName());
                    }
                }

                isZoneAdmin = CollectionUtils.containsAny(securityZone.getAdminUserGroups(), loggedInUsersGroups);
            }

            if (!isZoneAdmin && securityZone.getAdminRoles() != null) {
                isZoneAdmin = isUserOrUserGroupsInRole(userId, loggedInUsersGroups, securityZone.getAdminRoles());
            }
        }

        return isZoneAdmin;
    }

    public boolean isZoneAuditor(String zoneName) {
        boolean            isZoneAuditor = false;
        RangerSecurityZone securityZone  = null;

        try {
            securityZone = zoneStore.getSecurityZoneByName(zoneName);
        } catch (Exception e) {
            LOG.error("Unexpected error when fetching security zone with name:[{}] from database", zoneName, e);
        }

        if (securityZone != null) {
            String userId = rangerBizUtil.getCurrentUserLoginId();

            if (securityZone.getAuditUsers() != null && securityZone.getAuditUsers().contains(userId)) {
                isZoneAuditor = true;
            }

            Set<String> loggedInUsersGroups = Collections.emptySet();

            if (!isZoneAuditor && securityZone.getAuditUserGroups() != null) {
                List<XXGroupUser> groupUsers = groupUserDao.findByUserId(rangerBizUtil.getXUserId());

                loggedInUsersGroups = new HashSet<>();

                loggedInUsersGroups.add(GROUP_PUBLIC);

                if (groupUsers != null) {
                    for (XXGroupUser groupUser : groupUsers) {
                        loggedInUsersGroups.add(groupUser.getName());
                    }
                }

                isZoneAuditor = CollectionUtils.containsAny(securityZone.getAuditUserGroups(), loggedInUsersGroups);
            }

            if (!isZoneAuditor && securityZone.getAuditRoles() != null) {
                isZoneAuditor = isUserOrUserGroupsInRole(userId, loggedInUsersGroups, securityZone.getAuditRoles());
            }
        }

        return isZoneAuditor;
    }

    public RangerBaseService getRangerServiceByName(String serviceName, ServiceStore svcStore) throws Exception {
        LOG.debug("==> ServiceMgr.getRangerServiceByName({})", serviceName);

        RangerBaseService ret     = null;
        RangerService     service = svcStore == null ? null : svcStore.getServiceByName(serviceName);

        if (service != null) {
            ret = getRangerServiceByService(service, svcStore);
        } else {
            LOG.warn("ServiceMgr.getRangerServiceByName({}): could not find the service", serviceName);
        }

        LOG.debug("<== ServiceMgr.getRangerServiceByName({}): {}", serviceName, ret);

        return ret;
    }

    public RangerBaseService getRangerServiceByService(RangerService service, ServiceStore svcStore) throws Exception {
        LOG.debug("==> ServiceMgr.getRangerServiceByService({})", service);

        RangerBaseService ret         = null;
        String            serviceType = service == null ? null : service.getType();

        if (!StringUtils.isEmpty(serviceType)) {
            RangerServiceDef serviceDef = svcStore == null ? null : svcStore.getServiceDefByName(serviceType);

            if (serviceDef != null) {
                Class<? extends RangerBaseService> cls = getClassForServiceType(serviceDef);

                if (cls != null) {
                    ret = cls.newInstance();

                    ret.init(serviceDef, service);

                    if (ret instanceof RangerServiceTag) {
                        ((RangerServiceTag) ret).setTagStore(tagStore);
                    }
                } else {
                    LOG.warn("ServiceMgr.getRangerServiceByService({}): could not find service class '{}' for the service type '{}'", service, serviceDef.getImplClass(), serviceType);
                }
            } else {
                LOG.warn("ServiceMgr.getRangerServiceByService({}): could not find the service-def for the service type '{}'", service, serviceType);
            }
        } else {
            LOG.warn("ServiceMgr.getRangerServiceByService({}): could not find the service-type '{}'", service, serviceType);
        }

        LOG.debug("<== ServiceMgr.getRangerServiceByService({}): {}", service, ret);

        return ret;
    }

    long getTimeoutValueForLookupInMilliSeconds(RangerBaseService svc) {
        return getTimeoutValueInMilliSeconds("resource.lookup", svc, _DefaultTimeoutValue_Lookp);
    }

    long getTimeoutValueForValidateConfigInMilliSeconds(RangerBaseService svc) {
        return getTimeoutValueInMilliSeconds("validate.config", svc, _DefaultTimeoutValue_ValidateConfig);
    }

    long getTimeoutValueInMilliSeconds(final String type, RangerBaseService svc, long defaultValue) {
        LOG.debug("==> ServiceMgr.getTimeoutValueInMilliSeconds ({}, {})", type, svc);

        String              propertyName = type + ".timeout.value.in.ms"; // type == "lookup" || type == "validate-config"
        Long                result       = null;
        Map<String, String> config       = svc.getConfigs();

        if (config != null && config.containsKey(propertyName)) {
            result = parseLong(config.get(propertyName));
        }

        if (result != null) {
            LOG.debug("Found override in service config!");
        } else {
            String[] keys = new String[] {
                    "ranger.service." + svc.getServiceName() + "." + propertyName,
                    "ranger.servicetype." + svc.getServiceType() + "." + propertyName,
                    "ranger." + propertyName
            };

            for (String key : keys) {
                String value = PropertiesUtil.getProperty(key);

                if (value != null) {
                    result = parseLong(value);

                    if (result != null) {
                        LOG.debug("Using the value[{}] found in property[{}]", value, key);

                        break;
                    }
                }
            }
        }

        if (result == null) {
            LOG.debug("No overrides found in service config of properties file.  Using supplied default of[{}]!", defaultValue);

            result = defaultValue;
        }

        LOG.debug("<== ServiceMgr.getTimeoutValueInMilliSeconds ({}, {}): {}", type, svc, result);

        return result;
    }

    Long parseLong(String str) {
        try {
            return Long.valueOf(str);
        } catch (NumberFormatException e) {
            LOG.debug("ServiceMgr.parseLong: could not parse [{}] as Long! Returning null", str);

            return null;
        }
    }

    private static URL getValidURL(String urlString) {
        try {
            return new URL(urlString);
        } catch (Exception e) {
            return null;
        }
    }

    @SuppressWarnings("unchecked")
    private Class<? extends RangerBaseService> getClassForServiceType(RangerServiceDef serviceDef) {
        LOG.debug("==> ServiceMgr.getClassForServiceType({})", serviceDef);

        Class<? extends RangerBaseService> ret = null;

        if (serviceDef != null) {
            String serviceType = serviceDef.getName();

            ret = serviceTypeClassMap.get(serviceType);

            if (ret == null) {
                synchronized (serviceTypeClassMap) {
                    ret = serviceTypeClassMap.get(serviceType);

                    if (ret == null) {
                        String clsName = serviceDef.getImplClass();

                        LOG.debug("ServiceMgr.getClassForServiceType({}): service-class {} not found in cache", serviceType, clsName);

                        try {
                            if (StringUtils.isEmpty(clsName)) {
                                LOG.debug("No service-class configured for service-type:[{}], using RangerDefaultService", serviceType);

                                ret = RangerDefaultService.class;
                            } else {
                                URL[]          pluginFiles = getPluginFilesForServiceType(serviceType);
                                URLClassLoader clsLoader   = new URLClassLoader(pluginFiles, Thread.currentThread().getContextClassLoader());
                                Class<?>       cls         = Class.forName(clsName, true, clsLoader);

                                ret = (Class<? extends RangerBaseService>) cls;
                            }
                        } catch (Exception excp) {
                            LOG.warn("ServiceMgr.getClassForServiceType({}): failed to find service-class '{}'. Resource lookup will not be available. Using RangerDefaultService", serviceType, clsName, excp);

                            ret = RangerDefaultService.class;
                        }

                        serviceTypeClassMap.put(serviceType, ret);

                        LOG.debug("ServiceMgr.getClassForServiceType({}): service-class {} added to cache", serviceType, ret.getCanonicalName());
                    } else {
                        LOG.debug("ServiceMgr.getClassForServiceType({}): service-class {} found in cache", serviceType, ret.getCanonicalName());
                    }
                }
            } else {
                LOG.debug("ServiceMgr.getClassForServiceType({}): service-class {} found in cache", serviceType, ret.getCanonicalName());
            }
        }

        LOG.debug("<== ServiceMgr.getClassForServiceType({}): {}", serviceDef, ret);

        return ret;
    }

    private URL[] getPluginFilesForServiceType(String serviceType) {
        LOG.debug("==> ServiceMgr.getPluginFilesForServiceType({})", serviceType);

        List<URL> ret = new ArrayList<>();

        getFilesInDirectory("ranger-plugins/" + serviceType, ret);

        LOG.debug("<== ServiceMgr.getPluginFilesForServiceType({}): {} files", serviceType, ret.size());

        return ret.toArray(new URL[] {});
    }

    private void getFilesInDirectory(String dirPath, List<URL> files) {
        LOG.debug("==> ServiceMgr.getFilesInDirectory({})", dirPath);

        URL pluginJarPath = getClass().getClassLoader().getResource(dirPath);

        if (pluginJarPath != null && "file".equals(pluginJarPath.getProtocol())) {
            try {
                File[] dirFiles = new File(pluginJarPath.toURI()).listFiles();

                if (dirFiles != null) {
                    for (File dirFile : dirFiles) {
                        try {
                            URL jarPath = dirFile.toURI().toURL();

                            LOG.debug("getFilesInDirectory('{}'): adding {}", dirPath, dirFile.getAbsolutePath());

                            files.add(jarPath);
                        } catch (Exception excp) {
                            LOG.warn("getFilesInDirectory('{}'): failed to get URI for file {}", dirPath, dirFile.getAbsolutePath(), excp);
                        }
                    }
                }
            } catch (Exception excp) {
                LOG.warn("getFilesInDirectory('{}'): error", dirPath, excp);
            }
        } else {
            LOG.debug("getFilesInDirectory('{}'): could not find directory in CLASSPATH", dirPath);
        }

        LOG.debug("<== ServiceMgr.getFilesInDirectory({})", dirPath);
    }

    private VXResponse generateResponseForTestConn(Map<String, Object> responseData, String msg) {
        VXResponse vXResponse = new VXResponse();

        Long    objId              = null;
        boolean connectivityStatus = false;
        int     statusCode         = VXResponse.STATUS_ERROR;
        String  message            = msg;
        String  description        = msg;
        String  fieldName          = null;

        if (responseData != null) {
            if (responseData.get("objectId") != null) {
                objId = Long.parseLong(responseData.get("objectId").toString());
            }

            if (responseData.get("connectivityStatus") != null) {
                connectivityStatus = Boolean.parseBoolean(responseData.get("connectivityStatus").toString());
            }

            if (connectivityStatus) {
                statusCode = VXResponse.STATUS_SUCCESS;
            }

            if (responseData.get("message") != null) {
                message = responseData.get("message").toString();
            }

            if (responseData.get("description") != null) {
                description = responseData.get("description").toString();
            }

            if (responseData.get("fieldName") != null) {
                fieldName = responseData.get("fieldName").toString();
            }
        }

        VXMessage       vXMsg     = new VXMessage();
        List<VXMessage> vXMsgList = new ArrayList<>();

        vXMsg.setFieldName(fieldName);
        vXMsg.setMessage(message);
        vXMsg.setObjectId(objId);
        vXMsgList.add(vXMsg);

        vXResponse.setMessageList(vXMsgList);
        vXResponse.setMsgDesc(description);
        vXResponse.setStatusCode(statusCode);

        return vXResponse;
    }

    private boolean isUserOrUserGroupsInRole(String userId, Set<String> userGroups, List<String> roles) {
        boolean     ret         = false;
        RangerRoles rangerRoles = null;

        try {
            rangerRoles = rolesStore.getRoles("", -1L);
        } catch (Exception excp) {
            LOG.error("Unexpected error when fetching roles from database", excp);
        }

        if (rangerRoles != null) {
            RangerRolesUtil rolesUtil = new RangerRolesUtil(rangerRoles);
            Set<String>     userRoles = rolesUtil.getUserRoleMapping().get(userId);

            ret = userRoles != null && CollectionUtils.containsAny(roles, userRoles);

            if (!ret && userGroups != null) {
                for (String userGroup : userGroups) {
                    Set<String> groupRoles = rolesUtil.getGroupRoleMapping().get(userGroup);

                    ret = groupRoles != null && CollectionUtils.containsAny(roles, groupRoles);

                    if (ret) {
                        break;
                    }
                }
            }
        }

        return ret;
    }

    abstract static class TimedCallable<T> implements Callable<T> {
        final RangerBaseService svc;
        final Date              creation; // NOTE: This would be different from when the callable was actually offered to the executor

        public TimedCallable(RangerBaseService svc) {
            this.svc      = svc;
            this.creation = new Date();
        }

        @Override
        public T call() throws Exception {
            Date start = null;

            if (LOG.isDebugEnabled()) {
                start = new Date();

                LOG.debug("==> TimedCallable: {}", this);
            }

            ClassLoader clsLoader = Thread.currentThread().getContextClassLoader();

            try {
                Thread.currentThread().setContextClassLoader(svc.getClass().getClassLoader());

                return actualCall();
            } catch (Exception e) {
                LOG.error("TimedCallable.call: Error:{}", String.valueOf(e));

                throw e;
            } finally {
                Thread.currentThread().setContextClassLoader(clsLoader);

                if (start != null) {
                    Date finish        = new Date();
                    long waitTime      = start.getTime() - creation.getTime();
                    long executionTime = finish.getTime() - start.getTime();

                    LOG.debug("<== TimedCallable: {}: wait time[{} ms], execution time [{} ms]", this, waitTime, executionTime);
                }
            }
        }

        abstract T actualCall() throws Exception;
    }

    static class LookupCallable extends TimedCallable<List<String>> {
        final ResourceLookupContext context;

        public LookupCallable(final RangerBaseService svc, final ResourceLookupContext context) {
            super(svc);

            this.context = context;
        }

        @Override
        public String toString() {
            return String.format("lookup resource[%s] for service[%s], ", context.toString(), svc.getServiceName());
        }

        @Override
        public List<String> actualCall() throws Exception {
            return svc.lookupResource(context);
        }
    }

    static class ValidateCallable extends TimedCallable<Map<String, Object>> {
        public ValidateCallable(RangerBaseService svc) {
            super(svc);
        }

        @Override
        public String toString() {
            return String.format("validate config for service[%s]", svc.getServiceName());
        }

        @Override
        public Map<String, Object> actualCall() throws Exception {
            return svc.validateConfig();
        }
    }
}
